



#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <gio/gio.h>
#include <glib.h>
#include <agent.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <srtp2/srtp.h>

#include "xdtls_srtp.h"
#include "xcutil.h"
#include "xm_log.h"

//#include <pthread.h>
//#define dbgv(...) do{ printf("|%lx|", (long)pthread_self()); printf("<dtls>[D] " __VA_ARGS__); printf("\n"); fflush(stdout); }while(0)
//#define dbgi(...) do{ printf("|%lx|", (long)pthread_self()); printf("<dtls>[I] " __VA_ARGS__); printf("\n"); fflush(stdout); }while(0)
//#define dbge(...) do{ printf("|%lx|", (long)pthread_self()); printf("<dtls>[E] " __VA_ARGS__); printf("\n"); fflush(stdout); }while(0)

#define dtls_dbgv(FMT, ARGS...) dbgv("|%s| " FMT, dtls->obj_id, ##ARGS )
#define dtls_dbgi(FMT, ARGS...) dbgi("|%s| " FMT, dtls->obj_id, ##ARGS )
#define dtls_dbge(FMT, ARGS...) dbge("|%s| " FMT, dtls->obj_id, ##ARGS )



#define ZERO_ALLOC_(o, type, sz) do{o=(type)malloc(sz); memset(o, 0, sz);}while(0)



//
// DTLS and SRTP define
//
#define DTLS_CIPHERS  "ALL:NULL:eNULL:aNULL"
/* SRTP stuff (http://tools.ietf.org/html/rfc3711) */
#define SRTP_MASTER_KEY_LENGTH  16
#define SRTP_MASTER_SALT_LENGTH 14
#define SRTP_MASTER_LENGTH (SRTP_MASTER_KEY_LENGTH + SRTP_MASTER_SALT_LENGTH)


//
// lock define
//
typedef pthread_mutex_t xdtls_mutex;
static int lock_debug = 0;
#define xdtls_mutex_init(a) pthread_mutex_init(a,NULL)
#define xdtls_mutex_destroy(a) pthread_mutex_destroy(a)
/*! \brief Janus mutex lock without debug */
#define xdtls_mutex_lock_nodebug(a) pthread_mutex_lock(a);
/*! \brief Janus mutex lock with debug (prints the line that locked a mutex) */
#define xdtls_mutex_lock_debug(a) { gdbgi("[%s:%s:%d:] ", __FILE__, __FUNCTION__, __LINE__); gdbgi("LOCK %p\n", a); pthread_mutex_lock(a); };
/*! \brief Janus mutex lock wrapper (selective locking debug) */
#define xdtls_mutex_lock(a) { if(!lock_debug) { xdtls_mutex_lock_nodebug(a); } else { xdtls_mutex_lock_debug(a); } };
/*! \brief Janus mutex unlock without debug */
#define xdtls_mutex_unlock_nodebug(a) pthread_mutex_unlock(a);
/*! \brief Janus mutex unlock with debug (prints the line that unlocked a mutex) */
#define xdtls_mutex_unlock_debug(a) { gdbgi("[%s:%s:%d:] ", __FILE__, __FUNCTION__, __LINE__); gdbgi("UNLOCK %p\n", a); pthread_mutex_unlock(a); };
#define xdtls_mutex_unlock(a) { if(!lock_debug) { xdtls_mutex_unlock_nodebug(a); } else { xdtls_mutex_unlock_debug(a); } };


/*! \brief DTLS state */
typedef enum xdtls_state {
    XDTLS_STATE_FAILED = -1,
    XDTLS_STATE_CREATED,
    XDTLS_STATE_TRYING,
    XDTLS_STATE_CONNECTED,
} xdtls_state;


typedef struct xdtls_srtp_st {
    char obj_id[32];
    
    SSL_CTX * ssl_ctx;
    
    unsigned char fingerprint[EVP_MAX_MD_SIZE]; // EVP_MAX_MD_SIZE = 64
    int  fingerprint_len;
    char fingerprint_str[EVP_MAX_MD_SIZE*3+3];
    int fingerprint_str_len;
    
    /*! \brief Opaque pointer to the component this DTLS-SRTP context belongs to */
//    void *component;
    /*! \brief DTLS role of the gateway for this stream: 1=client, 0=server */
    xdtls_role dtls_role;
    /*! \brief DTLS state of this component: -1=failed, 0=nothing, 1=trying, 2=connected */
    xdtls_state dtls_state;
    /*! \brief Monotonic time of when the DTLS state has switched to connected */
    gint64 dtls_connected;
    /*! \brief SSL context used for DTLS for this component */
    SSL *ssl;
    /*! \brief Read BIO (incoming DTLS data) */
    BIO *read_bio;
    /*! \brief Write BIO (outgoing DTLS data) */
//    BIO *write_bio;
    /*! \brief Filter BIO (fix MTU fragmentation on outgoing DTLS data, if required) */
    BIO *filter_bio;
    /*! \brief Whether SRTP has been correctly set up for this component or not */
    gint srtp_valid;
    /*! \brief libsrtp context for incoming SRTP packets */
    srtp_t srtp_in;
    /*! \brief libsrtp context for outgoing SRTP packets */
    srtp_t srtp_out;
    /*! \brief libsrtp policy for incoming SRTP packets */
    srtp_policy_t remote_policy;
    /*! \brief libsrtp policy for outgoing SRTP packets */
    srtp_policy_t local_policy;
    /*! \brief Mutex to lock/unlock this libsrtp context */
//    xdtls_mutex srtp_mutex;
    /*! \brief Whether this DTLS stack is now ready to be used for messages as well (e.g., SCTP encapsulation) */
    int ready;
    
    xdtls_srtp_send_func_t send_func;
    void * cb_context;
    int handshake_started;
    int handshake_done;
    
    unsigned char remote_policy_key[SRTP_MASTER_LENGTH];
    unsigned char local_policy_key[SRTP_MASTER_LENGTH];

    X509 *dtls_cert;
    
} xdtls_srtp;




//
// global var
//
static xdtls_mutex * g_dtls_locks = NULL;;
//static const char * g_pem_file = NULL;
//static const char * g_key_file = NULL;
static X509 * g_cert = NULL;
static EVP_PKEY * g_key = NULL;

//static int mtu = 1472;
static int mtu = 1024;

static
int buf_to_hex_str_(const char * prefix, const unsigned char * data, int length, char* str_buf){
    char *p = (char *)str_buf;
    int i = 0;
    if(prefix){
        while(*prefix){
            *p = *prefix;
            prefix++;
            p++;
        }
    }
    for(i = 0; i < length; i++) {
        p += snprintf(p, 4,"%.2X:", data[i]);
        // p += 3;
    }
    *(p-1) = 0;
    return p-str_buf-1;
}

static void xdtls_cb_openssl_threadid(CRYPTO_THREADID *tid) {
    /* FIXME Assuming pthread, which is fine as GLib wraps pthread and
     * so that's what we use anyway? */
    pthread_t me = pthread_self();
    
    if(sizeof(me) == sizeof(void *)) {
        CRYPTO_THREADID_set_pointer(tid, (void *) me);
    } else {
        CRYPTO_THREADID_set_numeric(tid, (unsigned long) me);
    }
}

static void xdtls_cb_openssl_lock(int mode, int type, const char *file, int line) {
    if((mode & CRYPTO_LOCK)) {
        xdtls_mutex_lock(&g_dtls_locks[type]);
    } else {
        xdtls_mutex_unlock(&g_dtls_locks[type]);
    }
}

// copy from https://gist.github.com/MartinMReed/6393150
static
EVP_PKEY *generatePrivateKey()
{
    EVP_PKEY *pkey = NULL;
    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
    EVP_PKEY_keygen_init(pctx);
    EVP_PKEY_CTX_set_rsa_keygen_bits(pctx, 2048);
    EVP_PKEY_keygen(pctx, &pkey);
    return pkey;
}

static
X509 *generateCertificate(EVP_PKEY *pkey)
{
    X509 *x509 = X509_new();
    X509_set_version(x509, 2);
    ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
    X509_gmtime_adj(X509_get_notBefore(x509), 0);
    X509_gmtime_adj(X509_get_notAfter(x509), (long)60*60*24*365*10);
    X509_set_pubkey(x509, pkey);

    // generate different cname, or else firefox will complains
    static int cname_counter = 0;
    char cname_buf[32];
    int cname_len = snprintf(cname_buf, sizeof(cname_buf)-1, "xswitch-%010d", cname_counter++);
    cname_buf[cname_len] = '\0';

    X509_NAME *name = X509_get_subject_name(x509);
    X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (const unsigned char*)"CN", -1, -1, 0);
    X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (const unsigned char*)cname_buf, -1, -1, 0);
    X509_set_issuer_name(x509, name);
    X509_sign(x509, pkey, EVP_md5());
    return x509;
}

static
EVP_PKEY * PEM_load_private_key_from_file(const char * filename) {
    BIO *certbio = NULL;
    EVP_PKEY *key = NULL;
    int ret = -1;
    
    do{
        // read pem file and get local fingerprint
        certbio = BIO_new(BIO_s_file());
        if(certbio == NULL) {
            gdbge( "PEM_load_private_key_from_file: BIO error, file %s", filename);
            ret = -5;
            break;
        }
        
        if(BIO_read_filename(certbio, filename) == 0) {
            gdbge("PEM_load_private_key_from_file: Error reading (%s), file %s", ERR_reason_error_string(ERR_get_error()), filename);
            ret = -6;
            break;
        }
        
        key = PEM_read_bio_PrivateKey(certbio, NULL, NULL, NULL);
        if(key == NULL) {
            gdbge("PEM_load_private_key_from_file: Error reading (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -7;
            break;
        }
        
        
        ret = 0;
        
    }while(0);
    
    if(certbio){
        BIO_free_all(certbio);
    }
    
    if(ret){
        if(key){
            EVP_PKEY_free(key);
            key = NULL;
        }
    }
    
    
    return key;
}


static
X509 * X509_load_from_file(const char *pem_file) {
    BIO *certbio = NULL;
    X509 *cert = NULL;
    int ret = -1;
    
    do{
        // read pem file and get local fingerprint
        certbio = BIO_new(BIO_s_file());
        if(certbio == NULL) {
            gdbge( "load_X509: Certificate BIO error, file %s", pem_file);
            ret = -5;
            break;
        }
        
        if(BIO_read_filename(certbio, pem_file) == 0) {
            gdbge("load_X509: Error reading certificate (%s), file %s", ERR_reason_error_string(ERR_get_error()), pem_file);
            ret = -6;
            break;
        }
        
        cert = PEM_read_bio_X509(certbio, NULL, 0, NULL);
        if(cert == NULL) {
            gdbge("Error reading certificate (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -7;
            break;
        }
        
        
        ret = 0;
        
    }while(0);
    
    if(certbio){
        BIO_free_all(certbio);
    }
    
    if(ret){
        if(cert){
            X509_free(cert);
            cert = NULL;
        }
    }


    return cert;
}


static
const EVP_MD * get_hash_by_name(const char * hash_name ){
    if(strcasecmp(hash_name, XDTLS_HASH_SHA1) == 0){
        return EVP_sha1();
    }else if(strcasecmp(hash_name, XDTLS_HASH_SHA224) == 0){
        return EVP_sha224();
    }else if(strcasecmp(hash_name, XDTLS_HASH_SHA256) == 0){
        return EVP_sha256();
    }else if(strcasecmp(hash_name, XDTLS_HASH_SHA384) == 0){
        return EVP_sha384();
    }else if(strcasecmp(hash_name, XDTLS_HASH_SHA512) == 0){
        return EVP_sha512();
    }else{
        return NULL;
    }
}

static
int X509_fingerprint(X509 * cert, const char * hash_name, unsigned char *fingerprint, int * pfingerprint_len, char * fingerprint_str, int * pfingerprint_str_len) {
    int ret = -1;
    
    do{
        unsigned int size ;
        
        size = *pfingerprint_len;
        const EVP_MD * md = get_hash_by_name(hash_name);
        if(!md){
            gdbge("unknown hash name %s", hash_name);
            ret = -1;
            break;
        }
        
        if(X509_digest(cert, md, fingerprint, &size) == 0) {
            gdbge( "Error converting X509 structure (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -7;
            break;
        }
        *pfingerprint_len = size;
        
        if(fingerprint_str){
            *pfingerprint_str_len = buf_to_hex_str_(NULL, fingerprint, *pfingerprint_len, fingerprint_str);
        }
        
        ret = 0;

    }while(0);
    
    return ret;
}


int xdtls_srtp_init(const char *pem_file, const char *key_file) {
    
    int ret = -1;
    
    do{
        /* FIXME First of all make OpenSSL thread safe (see note above on issue #316) */
        ZERO_ALLOC_(g_dtls_locks, xdtls_mutex *, (sizeof(*g_dtls_locks) * CRYPTO_num_locks()));
        // g_dtls_locks = (xdtls_mutex *) malloc(sizeof(*g_dtls_locks) * CRYPTO_num_locks());
        int l=0;
        for(l = 0; l < CRYPTO_num_locks(); l++) {
            xdtls_mutex_init(&g_dtls_locks[l]);
        }
        
//        g_pem_file = strdup(pem_file);
//        g_key_file = strdup(key_file);
        
        if(key_file){
            g_cert = X509_load_from_file(pem_file);
            if(!g_cert){
                gdbge("fail to load cert file %s", pem_file);
                ret = -2;
                break;
            }
            gdbgi("load cert file ok, %s", pem_file);
            
            g_key = PEM_load_private_key_from_file(key_file);
            if(!g_key){
                gdbge("fail to load key file %s", key_file);
                ret = -3;
                break;
            }
            gdbgi("load key file ok, %s", key_file);
        }else{
            
            g_key = generatePrivateKey();
            if(!g_key){
                gdbgi("generate default private key fail!");
                ret = -4;
                break;
            }
            
            g_cert = generateCertificate(g_key);
            if(!g_key){
                gdbge("generate default cert fail!");
                ret = -4;
                break;
            }
            
            gdbgi("generate default key and cert ok");
        }

        
        
        
        CRYPTO_THREADID_set_callback(xdtls_cb_openssl_threadid);
        CRYPTO_set_locking_callback(xdtls_cb_openssl_lock);
        
        OpenSSL_add_ssl_algorithms();
        SSL_load_error_strings();
        
        /* Initialize libsrtp */
        if(srtp_init() != srtp_err_status_ok) {
            gdbge("Ops, error setting up libsrtp?\n");
            ret = -7;
            break;
        }
        
        ret = 0;
    }while(0);
    
    return ret;
}

void xdtls_srtp_exit(){
    if(g_cert){
        X509_free(g_cert);
        g_cert = NULL;
    }
    
    if(g_key){
        EVP_PKEY_free(g_key);
        g_key = NULL;
    }
    
    if(g_dtls_locks){
        free(g_dtls_locks);
        g_dtls_locks = NULL;
    }
}



static int xdtls_bio_filter_write(BIO *h, const char *buf,int num);
static long xdtls_bio_filter_ctrl(BIO *h, int cmd, long arg1, void *arg2);
static int xdtls_bio_filter_new(BIO *h);
static int xdtls_bio_filter_free(BIO *data);

static BIO_METHOD *xdtls_filter_bio_method(void) {
    static BIO_METHOD* g_bio_filter_methods = BIO_meth_new(BIO_TYPE_BIO, "xdtls-srtp filter");
    BIO_meth_set_write(g_bio_filter_methods, xdtls_bio_filter_write);
    BIO_meth_set_read(g_bio_filter_methods, NULL);
    BIO_meth_set_puts(g_bio_filter_methods, NULL);
    BIO_meth_set_gets(g_bio_filter_methods, NULL);
    BIO_meth_set_ctrl(g_bio_filter_methods, xdtls_bio_filter_ctrl);
    BIO_meth_set_create(g_bio_filter_methods, xdtls_bio_filter_new);
    BIO_meth_set_destroy(g_bio_filter_methods, xdtls_bio_filter_free);
    BIO_meth_set_callback_ctrl(g_bio_filter_methods, NULL);

    return g_bio_filter_methods;
}

typedef struct xdtls_bio_filter {
    xdtls_srtp * dtls;
} *xdtls_bio_filter_t;


static int xdtls_bio_filter_new(BIO *bio) {
    /* Create a filter state struct */
    xdtls_bio_filter_t filter = (xdtls_bio_filter_t) g_malloc0(sizeof(xdtls_bio_filter));
    
    /* Set the BIO as initialized */
    BIO_set_init(bio, 1);
    BIO_set_data(bio, filter);
    BIO_set_flags(bio, 0);
    
    return 1;
}

static int xdtls_bio_filter_free(BIO *bio) {
    if(bio == NULL){
        return 0;
    }
    
    xdtls_bio_filter_t filter = (xdtls_bio_filter_t)BIO_get_data(bio);
    if(filter) {
        g_free(filter);
    }

    BIO_set_data(bio, NULL);
    BIO_set_init(bio, 0);
    BIO_set_flags(bio, 0);

    return 1;
}

static int xdtls_bio_filter_write(BIO *bio, const char *in, int inl) {
    
    xdtls_bio_filter_t filter = (xdtls_bio_filter_t)BIO_get_data(bio);
    xdtls_srtp_t dtls = filter->dtls;
    if(dtls->send_func){
        dtls_dbgv("xdtls_bio_filter_write: send %p, %d\n", in, inl);
        dtls->send_func(dtls, dtls->cb_context, (const unsigned char *)in, inl);
    }else{
        dtls_dbgv("xdtls_bio_filter_write: skip %p, %d\n", in, inl);
    }
    return inl;
}

static long xdtls_bio_filter_ctrl(BIO *bio, int cmd, long num, void *ptr) {
    // see BIO_CTRL_RESET at https://github.com/openssl/openssl/blob/master/include/openssl/bio.h
    //
    
    xdtls_bio_filter_t filter = (xdtls_bio_filter_t)BIO_get_data(bio);
    xdtls_srtp_t dtls = filter->dtls;
    dtls_dbgv("xdtls_bio_filter_ctrl: cmd=%d, num=%ld", cmd, num);
    switch(cmd) {
        case BIO_CTRL_FLUSH:
            /* The OpenSSL library needs this */
            return 1;
        case BIO_CTRL_DGRAM_QUERY_MTU:
            /* Let's force the MTU that was configured */
//            dtls_dbgv( "Advertizing MTU: %d\n", mtu);
            return mtu;
        case BIO_CTRL_WPENDING:
            return 0L;
        case BIO_CTRL_PENDING: {
            dtls_dbge("should not reach here");
        }
        default: break;
            
    }
    return 0;
}



/* DTLS alert callback */
static void xdtls_info_callback(const SSL *ssl, int where, int ret) {
    // see SSL_CB_LOOP at https://github.com/openssl/openssl/blob/c42a78cb57cd335f3e2b224d4d8c8d7c2ecfaa44/include/openssl/ssl.h
    // see https://linux.die.net/man/3/ssl_set_info_callback
    
    xdtls_srtp_t dtls = (xdtls_srtp_t)SSL_get_ex_data(ssl, 0);
    dtls->ssl_ctx = dtls->ssl_ctx; // for suppress warning
    dtls_dbgv("xdtls_info_callback: where=0x%X, ret = %d", where, ret);
//    if (!(where & SSL_CB_ALERT)) {
//        return;
//    }
    
    const char *str;
    int w;
    
    w=where& ~SSL_ST_MASK;
    
    if (w & SSL_ST_CONNECT) str="SSL_connect";
    else if (w & SSL_ST_ACCEPT) str="SSL_accept";
    else str="undefined";
    
    if (where & SSL_CB_LOOP){
        dtls_dbgv("%s:%s\n",str,SSL_state_string_long(ssl));
    }else if (where & SSL_CB_ALERT){
        str=(where & SSL_CB_READ)?"read":"write";
        dtls_dbgv("SSL3 alert %s:%s:%s\n",
                   str,
                   SSL_alert_type_string_long(ret),
                   SSL_alert_desc_string_long(ret));
    }else if (where & SSL_CB_EXIT){
        if (ret == 0){
            dtls_dbgv("%s:failed in %s\n",str,SSL_state_string_long(ssl));
        }else if (ret < 0){
            dtls_dbgv("%s:error in %s\n",str,SSL_state_string_long(ssl));
        }else{
            dtls_dbgv("%s:exit in %s\n",str,SSL_state_string_long(ssl));
        }
    }else{
        dtls_dbgv("%s:%s\n",str,SSL_state_string_long(ssl));
    }
    
}

/* DTLS certificate verification callback */
static
int xdtls_verify_callback(int preverify_ok, X509_STORE_CTX *ctx) {
    /* We just use the verify_callback to request a certificate from the client */
    const SSL *ssl = (const SSL *)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
    xdtls_srtp_t dtls = (xdtls_srtp_t)SSL_get_ex_data(ssl, 0);
    dtls_dbgv("xdtls_verify_callback: preverify_ok=%d, x509ctx=%p", preverify_ok, ctx);
    return 1;
}

void xdtls_srtp_destroy(xdtls_srtp_t dtls) {
    if(!dtls) return;

    dtls->ready = 0;

    /* Destroy DTLS stack and free resources */
//    dtls->component = NULL;
    if(dtls->ssl != NULL) {
        SSL_free(dtls->ssl);
        dtls->ssl = NULL;
    }
    /* BIOs are destroyed by SSL_free */
    dtls->read_bio = NULL;
//    dtls->write_bio = NULL;
    dtls->filter_bio = NULL;
    if(dtls->srtp_valid) {
        if(dtls->srtp_in) {
            srtp_dealloc(dtls->srtp_in);
            dtls->srtp_in = NULL;
        }
        if(dtls->srtp_out) {
            srtp_dealloc(dtls->srtp_out);
            dtls->srtp_out = NULL;
        }
        /* FIXME What about dtls->remote_policy and dtls->local_policy? */
    }
    
    if(dtls->ssl_ctx){
        SSL_CTX_free(dtls->ssl_ctx);
    }

    if (dtls->dtls_cert) {
        X509_free(dtls->dtls_cert);
        dtls->dtls_cert = nullptr;
    }

    free(dtls);
}

static inline
uint64_t get_next_dtls_id(){
    static uint64_t next_id = 0;
    return ++next_id;
}

xdtls_srtp_t xdtls_srtp_create(const char *pem_file, const char *key_file, const char * hash_name, xdtls_role role, xdtls_srtp_send_func_t send_func, void * cb_context) {
    xdtls_srtp *dtls = NULL;
    BIO *certbio = NULL;
    X509 *cert = NULL;
    EVP_PKEY * key = NULL;
    EC_KEY* ecdh = NULL;
    int ret = -1;
    
    do{
        ZERO_ALLOC_(dtls, xdtls_srtp_t, sizeof(xdtls_srtp));
        if(!dtls) {
            gdbge("Memory error!\n");
            ret = -1;
            break;
        }
        snprintf(dtls->obj_id, sizeof(dtls->obj_id), "dtls-%lu", get_next_dtls_id());
        dtls->send_func = send_func;
        dtls->cb_context = cb_context;
        
        
        /* create the DTLS context */
        dtls->ssl_ctx = SSL_CTX_new(DTLSv1_2_method());
        // dtls->ssl_ctx = SSL_CTX_new(DTLSv1_server_method());
        if(!dtls->ssl_ctx) {
            dtls_dbge("Ops, error creating DTLS context?\n");
            ret =  -1;
            break;
        }
        
        SSL_CTX_set_verify(dtls->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, xdtls_verify_callback);
        SSL_CTX_set_tlsext_use_srtp(dtls->ssl_ctx, "SRTP_AES128_CM_SHA1_80"); /* FIXME Should we support something else as well? */
        
        
        // load pem and key file, and check them
        X509 * cert_used = NULL;
        EVP_PKEY * key_used = NULL;

        if(pem_file){
            cert = X509_load_from_file(pem_file);
            if(!cert){
                dtls_dbge("fail to load cert file %s", pem_file);
                ret = -2;
                break;
            }
            cert_used = cert;
        }else{
            cert_used = generateCertificate(g_key);
            dtls->dtls_cert = cert_used;
        }

        if(key_file){
            key = PEM_load_private_key_from_file(key_file);
            if(!key){
                dtls_dbge("fail to load key file %s", key_file);
                ret = -3;
                break;
            }
            key_used = key;
        }else{
            key_used = g_key;
        }

        if(!SSL_CTX_use_certificate(dtls->ssl_ctx, cert_used)) {
            dtls_dbge("Certificate error (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -3;
            break;
        }
        
//        const char * hash_name = "sha-256";
        ret = X509_fingerprint(cert_used, hash_name, dtls->fingerprint, &dtls->fingerprint_len, dtls->fingerprint_str, &dtls->fingerprint_str_len);
        if(ret){
            dtls_dbge("get fingerprint fail (%s)", hash_name);
            ret = -4;
            break;
        }
        dtls_dbgv("local fingerprint (%s, %d bytes): %s", hash_name, dtls->fingerprint_str_len, dtls->fingerprint_str);
        
        
        if(!SSL_CTX_use_PrivateKey(dtls->ssl_ctx, key_used)) {
            dtls_dbge("Certificate key error (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -6;
            break;
        }

        if(!SSL_CTX_check_private_key(dtls->ssl_ctx)) {
            dtls_dbge("Certificate check error (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -7;
            break;
        }
        
        // enable read ahead for good performance
        SSL_CTX_set_read_ahead(dtls->ssl_ctx,1);
        SSL_CTX_set_cipher_list(dtls->ssl_ctx, DTLS_CIPHERS);
        
        
        /* Create SSL */
        dtls->ssl = SSL_new(dtls->ssl_ctx);
        if(!dtls->ssl) {
            dtls_dbge("Error creating DTLS session! (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -10;
            break;
        }
        SSL_set_ex_data(dtls->ssl, 0, dtls);
        SSL_set_info_callback(dtls->ssl, xdtls_info_callback);
        
        dtls->read_bio = BIO_new(BIO_s_mem());
        if(!dtls->read_bio) {
            dtls_dbge("Error creating read BIO! (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -20;
            break;
        }
        BIO_set_mem_eof_return(dtls->read_bio, -1);
        
//        dtls->write_bio = BIO_new(BIO_s_mem());
//        if(!dtls->write_bio) {
//            dtls_dbge("Error creating write BIO! (%s)\n", ERR_reason_error_string(ERR_get_error()));
//            ret = -30;
//            break;
//        }
//        BIO_set_mem_eof_return(dtls->write_bio, -1);
        
        /* The write BIO needs our custom filter, or fragmentation won't work */
        dtls->filter_bio = BIO_new(xdtls_filter_bio_method());
        if(!dtls->filter_bio) {
            dtls_dbge("Error creating filter BIO! (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -40;
            break;
        }
        xdtls_bio_filter_t filter = (xdtls_bio_filter_t)BIO_get_data(dtls->filter_bio);
        filter ->dtls = dtls;
        
        /* Chain filter and write BIOs */
//        BIO_push(dtls->filter_bio, dtls->write_bio);
        /* Set the filter as the BIO to use for outgoing data */
        SSL_set_bio(dtls->ssl, dtls->read_bio, dtls->filter_bio);
        dtls->dtls_role = role;
        if(dtls->dtls_role == XDTLS_ROLE_CLIENT) {
            dtls_dbgv("Setting connect state (DTLS client)");
            SSL_set_connect_state(dtls->ssl);
        } else {
            dtls_dbgv("Setting accept state (DTLS server)");
            SSL_set_accept_state(dtls->ssl);
        }
        
        /* https://code.google.com/p/chromium/issues/detail?id=406458
         * Specify an ECDH group for ECDHE ciphers, otherwise they cannot be
         * negotiated when acting as the server. Use NIST's P-256 which is
         * commonly supported.
         */
        ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
        if(ecdh == NULL) {
            dtls_dbge("Error creating ECDH group! (%s)\n", ERR_reason_error_string(ERR_get_error()));
            ret = -50;
            break;
        }
        SSL_set_options(dtls->ssl, SSL_OP_SINGLE_ECDH_USE);
        SSL_set_tmp_ecdh(dtls->ssl, ecdh);
        dtls->ready = 0;
        
//        xdtls_mutex_init(&dtls->srtp_mutex);
        
        
        ret = 0;

    }while(0);
    
    if(ret){
        xdtls_srtp_destroy(dtls);
        dtls = NULL;
    }
    
    if(ecdh){
        EC_KEY_free(ecdh);
    }
    if(cert){
        X509_free(cert);
    }
    if(certbio){
        BIO_free_all(certbio);
    }
    return dtls;
}

const char * xdtls_srtp_get_id(xdtls_srtp_t dtls){
    return dtls->obj_id;
}

static inline
void print_buf_hex(xdtls_srtp_t dtls, const char * prefix, const unsigned char * data, int length){
    char msg[1024] = {0,};
    char *rfp = (char *)&msg;
    int i = 0;
    for(i = 0; i < length; i++) {
        rfp += g_snprintf(rfp, 3, "%.2x", data[i]);
    }
    dtls_dbgi("%s%s", prefix, msg);
}

static
int setup_srtp_keys(xdtls_srtp * dtls){
    /* Build master keys and set SRTP policies */
    /* Remote (inbound) */
    srtp_crypto_policy_set_rtp_default(&(dtls->remote_policy.rtp));
    srtp_crypto_policy_set_rtcp_default(&(dtls->remote_policy.rtcp));
    dtls->remote_policy.ssrc.type = ssrc_any_inbound;
    //    unsigned char remote_policy_key[SRTP_MASTER_LENGTH];
    dtls->remote_policy.key = (unsigned char *)dtls->remote_policy_key;
    
#if HAS_DTLS_WINDOW_SIZE
    dtls->remote_policy.window_size = 1280;
    dtls->remote_policy.allow_repeat_tx = 1;
#endif
    dtls->remote_policy.next = NULL;
    /* Local (outbound) */
    srtp_crypto_policy_set_rtp_default(&(dtls->local_policy.rtp));
    srtp_crypto_policy_set_rtcp_default(&(dtls->local_policy.rtcp));
    dtls->local_policy.ssrc.type = ssrc_any_outbound;
    //    unsigned char local_policy_key[SRTP_MASTER_LENGTH];
    dtls->local_policy.key = (unsigned char *)dtls->local_policy_key;
#if HAS_DTLS_WINDOW_SIZE
    dtls->local_policy.window_size = 1280;
    dtls->local_policy.allow_repeat_tx = 1;
#endif
    dtls->local_policy.next = NULL;
    /* Create SRTP sessions */
    srtp_err_status_t res = srtp_create(&(dtls->srtp_in), &(dtls->remote_policy));
    dtls_dbgv("creating inbound srtp %s", res == srtp_err_status_ok ? "ok" : "fail");
    if(res != 0){
        return -1;
    }
    
    res = srtp_create(&(dtls->srtp_out), &(dtls->local_policy));
    dtls_dbgv("creating outbound srtp %s", res == srtp_err_status_ok ? "ok" : "fail");
    if(res != 0){
        return -1;
    }
    
    dtls->srtp_valid = 1;
    return 0;

}

static
void setup_srtp(xdtls_srtp * dtls){
    X509 *rcert = SSL_get_peer_certificate(dtls->ssl);
    if(!rcert) {
        dtls_dbge( "No remote certificate?? (%s)\n",  ERR_reason_error_string(ERR_get_error()));
        return;
    }
    unsigned int rsize;
    unsigned char rfingerprint[EVP_MAX_MD_SIZE];
    
    
    X509_digest(rcert, EVP_sha256(), (unsigned char *)rfingerprint, &rsize);
    X509_free(rcert);
    rcert = NULL;
    
    print_buf_hex(dtls, "Remote fingerprint(sha-256): ", rfingerprint, rsize);
    
    /* Complete with SRTP setup */
    unsigned char material[SRTP_MASTER_LENGTH*2];
    unsigned char *local_key, *local_salt, *remote_key, *remote_salt;
    memset(material, 0, SRTP_MASTER_LENGTH*2 );
    /* Export keying material for SRTP */
    if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LENGTH*2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) {
        /* Oops... */
        dtls_dbge(" Oops, couldn't extract SRTP keying material ?? (%s)", ERR_reason_error_string(ERR_get_error()));
        return;
    }
    
    /* Key derivation (http://tools.ietf.org/html/rfc5764#section-4.2) */
    
    if(dtls->dtls_role == XDTLS_ROLE_CLIENT) {
        local_key = material;
        remote_key = local_key + SRTP_MASTER_KEY_LENGTH;
        local_salt = remote_key + SRTP_MASTER_KEY_LENGTH;
        remote_salt = local_salt + SRTP_MASTER_SALT_LENGTH;
    } else {
        remote_key = material;
        local_key = remote_key + SRTP_MASTER_KEY_LENGTH;
        remote_salt = local_key + SRTP_MASTER_KEY_LENGTH;
        local_salt = remote_salt + SRTP_MASTER_SALT_LENGTH;
    }
    
    print_buf_hex(dtls, "local_key: ", local_key, SRTP_MASTER_KEY_LENGTH);
    print_buf_hex(dtls, "local_salt: ", local_salt, SRTP_MASTER_SALT_LENGTH);
    print_buf_hex(dtls, "remote_key: ", remote_key, SRTP_MASTER_KEY_LENGTH);
    print_buf_hex(dtls, "remote_salt: ", remote_salt, SRTP_MASTER_SALT_LENGTH);
    
    memcpy(dtls->remote_policy_key, remote_key, SRTP_MASTER_KEY_LENGTH);
    memcpy(dtls->remote_policy_key + SRTP_MASTER_KEY_LENGTH, remote_salt, SRTP_MASTER_SALT_LENGTH);
    
    memcpy(dtls->local_policy_key, local_key, SRTP_MASTER_KEY_LENGTH);
    memcpy(dtls->local_policy_key + SRTP_MASTER_KEY_LENGTH, local_salt, SRTP_MASTER_SALT_LENGTH);
    
    setup_srtp_keys(dtls);
}

const char * xdtls_srtp_get_local_fingerprint_str(xdtls_srtp_t dtls){
    return dtls->fingerprint_str;
}

#include <chrono>
#include <thread>
int xdtls_srtp_start_handshake(xdtls_srtp_t dtls, int is_force){
    if(!dtls->handshake_started || is_force){
        dtls->handshake_started = 1;
        if(is_force){
            SSL_renegotiate(dtls->ssl);
        }
        SSL_do_handshake(dtls->ssl);
        
//        int64_t timeout_value = 0;
//        do{
//            if(timeout_value > 0){
//                dtls_dbgv("sleep for %lld ms", timeout_value);
//                std::this_thread::sleep_for(std::chrono::milliseconds(timeout_value));
//            }
//            struct timeval timeout = {0};
//            DTLSv1_get_timeout(dtls->ssl, &timeout);
//            timeout_value = timeout.tv_sec*1000 + timeout.tv_usec/1000;
//
//        }while(timeout_value > 0);
//        dtls_dbgv("dtls timeout");
//        DTLSv1_handle_timeout(dtls->ssl);
        
    }
    return 0;
}


int xdtls_srtp_input_data(xdtls_srtp_t dtls, const unsigned char * buf, int len, int * psetup){
    int written = BIO_write(dtls->read_bio, buf, len);
    dtls_dbgv("write read_bio %d/%d", written, len);
    
    char data[1500];  /* FIXME */
    memset(&data, 0, 1500);
    int read = SSL_read(dtls->ssl, &data, 1500);
    int init_done = SSL_is_init_finished(dtls->ssl);
    dtls_dbgv("ssl read=%d, init_done=%d, state=%s (SSL_ST_OK=%d)", read, init_done, SSL_state_string(dtls->ssl), SSL_get_state(dtls->ssl)==TLS_ST_OK);
    if(init_done && !dtls->handshake_done){
        dtls->handshake_done = 1;
        setup_srtp(dtls);
        *psetup = 1;
    }else{
        *psetup = 0;
    }
    return 0;
}


int xdtls_srtp_is_ready(xdtls_srtp_t dtls){
    return dtls->srtp_valid;
}

int xdtls_srtp_protect(xdtls_srtp_t dtls, unsigned char * buf, int len){
    if(!dtls->srtp_valid){
        return -1;
    }
    
    srtp_err_status_t res = srtp_protect(dtls->srtp_out, buf, &len);
    if(res != srtp_err_status_ok){
        dtls_dbge("srtp_protect fail, res=%d", res);
        return 0 - res;
    }
//    dtls_dbgv("rtp protect len %d", len);
    return len;
}

int xdtls_srtp_protect_rtcp(xdtls_srtp_t dtls, unsigned char * buf, int len){
    if(!dtls->srtp_valid){
        return -1;
    }
    
    srtp_err_status_t res = srtp_protect_rtcp(dtls->srtp_out, buf, &len);
    if(res != srtp_err_status_ok){
        dtls_dbge("srtp_protect_rtcp fail, res=%d", res);
        return 0 - res;
    }
    //    dtls_dbgv("rtcp protect len %d", len);
    return len;
}

int xdtls_srtp_unprotect(xdtls_srtp_t dtls, unsigned char * buf, int len){
    if(!dtls->srtp_valid){
        return -1;
    }
    
    int buflen = len;
    srtp_err_status_t res = srtp_unprotect(dtls->srtp_in, buf, &buflen);
    if(res != srtp_err_status_ok){
        unsigned short seq = be_get_u16(buf+2);
        dtls_dbge("srtp_unprotect fail, res=%d, seq=%u, len=%d", res, seq, buflen);
        return -2;
    }
//    buf[buflen] = 0;
//    dtls_dbgv("rtp unprotect len %d, msg=%s", buflen, buf+12);
    return buflen;
}

int xdtls_srtp_unprotect_rtcp(xdtls_srtp_t dtls, unsigned char * buf, int len){
    if(!dtls->srtp_valid){
        return -1;
    }
    
    int buflen = len;
    srtp_err_status_t res = srtp_unprotect_rtcp(dtls->srtp_in, buf, &buflen);
    if(res != srtp_err_status_ok){
        dtls_dbge("srtp_unprotect_rtcp fail, res=%d, len=%d", res, buflen);
        return -2;
    }
    //    buf[buflen] = 0;
    //    dtls_dbgv("rtp unprotect len %d, msg=%s", buflen, buf+12);
    return buflen;
}


int xdtls_srtp_get_keys(xdtls_srtp_t dtls, unsigned char ** plocal, int * plocal_len, unsigned char ** premote, int *premote_len){
    if(!dtls->srtp_valid) return -1;
    *plocal = dtls->local_policy_key;
    *plocal_len = SRTP_MASTER_LENGTH;
    *premote = dtls->remote_policy_key;
    *premote_len = SRTP_MASTER_LENGTH;
    return 0;
}

int xdtls_srtp_set_keys(xdtls_srtp_t dtls, unsigned char * local, int local_len, unsigned char * remote, int remote_len){
    if(dtls->srtp_valid) return -1;
    
    if(local_len != SRTP_MASTER_LENGTH || remote_len != SRTP_MASTER_LENGTH ){
        return -2;
    }
    
    memcpy(dtls->local_policy_key, local, local_len);
    memcpy(dtls->remote_policy_key, remote, local_len);
    int ret = setup_srtp_keys(dtls);
    if(ret){
        return -3;
    }
    
    return 0;
}

// TODO: check remote fingerprint
int xdtls_srtp_set_remote_fingerprint_str(xdtls_srtp_t dtls, const char * hash_name, const char * fingerprint_str){
    return 0;
}

int xdtls_srtp_check_timeout(xdtls_srtp_t dtls, int64_t * ptimeout_value){
//    DTLSv1_handle_timeout(dtls->ssl);
//    return 1;
    
    struct timeval timeout = {0};
    DTLSv1_get_timeout(dtls->ssl, &timeout);
    int64_t timeout_value = timeout.tv_sec*1000 + timeout.tv_usec/1000;
    *ptimeout_value = timeout_value;
    
    if(timeout_value == 0){
        
        DTLSv1_handle_timeout(dtls->ssl);
        
        // get timeout again
        struct timeval timeout = {0};
        DTLSv1_get_timeout(dtls->ssl, &timeout);
        int64_t timeout_value = timeout.tv_sec*1000 + timeout.tv_usec/1000;
        *ptimeout_value = timeout_value;
        
        return 1;
    }
    return 0;
    
}

